#include <string>
#include <fstream>
#include <sstream>
#include <cassert>
#include <algorithm>
#include <limits>
#include <math.h>

#include "scene.h"
//#include "material.h"
using namespace std;
namespace gemren
{
	
	scene::scene(const color_converter& conv):
			conv(conv),
			accel(embree::null), 
			tri_cnt(0), nor_cnt(0), tex_cnt(0),ver_cnt(0),
			triangles(0), 
			vertices(0),
			normals(0),
			texcoords(0), 
			lighting_mode(LM_PRIMARY_LIGHTSOURCE | LM_CAUSTIC_BRDF)
	{
	}
	scene::scene(): 
			accel(embree::null), 
			tri_cnt(0), nor_cnt(0), tex_cnt(0),ver_cnt(0),
			triangles(0), 
			vertices(0),
			normals(0),
			texcoords(0),
			lighting_mode(LM_PRIMARY_LIGHTSOURCE | LM_CAUSTIC_BRDF)
	{
	}

	void deleteptr(void* ptr)
	{// for for_each
		delete ptr;
	}
	template<typename K, typename V>
	void delete_pair_val(std::pair<K, V> p)
	{
		delete p.second;
	}
	scene::~scene()
	{
		delete[] lightprobs;

		for_each(materials.begin(), materials.end(), delete_pair_val<std::string, material_ptr>);

		delete[] triangles;
		delete[] vertices;
		delete[] normals;
		delete[] texcoords;
	}
	void scene::store_material(const string& name, material_ptr mat)
		{
			stringstream ss(name);
			int count = 0;
			while(materials.find(ss.str()) != materials.end())
			{ //find unique name
				ss.str("");
				ss<<name<<" ("<< ++count <<")";
			}
			materials.insert(pair<string, material*>(ss.str(), mat)); 
			light_emitting_material* light = dynamic_cast<light_emitting_material*> (mat);
			if(light) lights.push_back((light_source*)light);
		}
	float scene::light_selection_probability(const light_emitting_material* lmat) const
	{
		float total = 0;
		for(size_t i = 0; i< lights.size(); ++i)
			total += lights[i]->get_intensity().integral();
		return lmat->get_intensity().integral() / total;
	}
	void scene::direct_lighting(random* ran, intersection* is)const
	{
		if(!is->triptr->insidemat->is_directly_lighted() || lights.size()==0) return;
		  
		if(!is->diffuse_reflection)
			if((is->lighting_mode & LM_CAUSTIC_LIGHTSOURCE) || (is->lighting_mode & LM_CAUSTIC_MIS))
			{
				lightpaths.compute_lighting(this, ran, is);
			}
		if((is->lighting_mode & LM_PRIMARY_LIGHTSOURCE) || (is->lighting_mode & LM_PRIMARY_MIS))
		{
			float total = 0;
			for(size_t i = 0; i< lights.size(); ++i)
				lightprobs[i] = (total += lights[i]->get_intensity().integral());
			float choice = ran->getf()*total;
			int chi = 0;
			while(lightprobs[chi] < choice) ++chi;
			lights[chi]->enlighten_spot(this, ran, is, total/(chi==0?lightprobs[0]:lightprobs[chi]-lightprobs[chi-1]));
		}
	}
	void scene::build_light_paths()
	{
		lightpaths.find_paths(this);
	}
	size_t scene::get_light_triangle(random& r) const
	{
		return 0;
		if(lbegin()==lend()) return size_t(-1);
		light_emitting_material* mat = (light_emitting_material*) lights[r.get()% (lights.size())]; //todo remove reinterpret
		return mat->get_triangle(r);
	}
	
	coord scene::triangle_area_sample(random& r, size_t tri)
	{
		const triangle& t = get_triangle(tri);
		coord2d samcoord = r.uniform_triangle();
		const coord& v0 = get_vertex(t.v0);
		return v0 + (get_vertex(t.v1) - v0)*samcoord.x + (get_vertex(t.v2) - v0)*samcoord.y;
	}
	void scene::add_geometry(   const std::list<triangle>& tris, 
								const std::list<coord>& verts,
								const std::list<coord>& norms,
								const std::list<coord2d>& texs)
	{ //add triangles with correct vertex offsets
		size_t 
			tx = tri_cnt,
			vc = ver_cnt,
			nc = nor_cnt, 
			tc = tex_cnt; //offsets of new elements
		add_elms(vertices, ver_cnt, verts);
		add_elms(normals, nor_cnt, norms);
		add_elms(texcoords, tex_cnt, texs);
		add_elms(triangles, tri_cnt, tris);
		
		//repair offsets
		triangle* begin = triangles + tx,
			*end = triangles+tri_cnt;
	//	size_t index = tx;
		while(begin!=end)
		{
			light_emitting_material* light = dynamic_cast<light_emitting_material*> (begin->insidemat);
			if(light) light->add_emitting_triangle(tx);
			
			begin->v0 += vc;
			begin->v1 += vc;
			begin->v2 += vc;

			begin->n0 += nc;
			begin->n1 += nc;
			begin->n2 += nc;
			
			begin->t0 += tc;
			begin->t1 += tc;
			begin->t2 += tc;
			
			begin->normal = embree::normalize(embree::cross(vertices[begin->v1]-vertices[begin->v0],vertices[begin->v2]-vertices[begin->v0]));
			
			++begin;
			++tx;

		}
		
		
	}
	/*
	void scene::set_background(float_type distance, shader_ptr shader)
	{
		background_distance = distance;
		background_shader = shader;
	}
	*/
	void scene::build_accel()
	{
		gemren::ensure_scheduler_init();
		embree::BuildTriangle* tris = (embree::BuildTriangle*) embree::alignedMalloc(tri_cnt*sizeof(embree::BuildTriangle));
		for(size_t i = 0; i < tri_cnt; ++i)
		{
			triangle& t = triangles[i];
			tris[i] = embree::BuildTriangle(vertices[t.v0], vertices[t.v1], vertices[t.v2],int(i));			
		}
		string type = "default";
		accel = embree::rtcCreateAccel(type.c_str(), tris, tri_cnt);
		this->lightprobs = new float[lights.size()];
	}

	bool scene::point_occluded(const coord& src, const coord& dest, const float near, const float far) const 
	{
		if(!this->accel) return 0;
		return accel->occluded( embree::Ray(src, dest, near, far)); //mayber set far boundary?
	}
	bool scene::first_intersection(intersection& i, const coord& p0, const coord& p1, float from, float to) const
	{ 
		if(!this->accel) return 0;
		embree::Ray r(p0,p1,from, to);
		i.prevtri = i.id0;
		i.id0= -1;
		accel->intersect(r,i);
		if(i)
		{
			i.triptr = triangles+i.id0;
			//i.lighting_mode = lighting_mode;
			triangle& t = *i.triptr;
			i.normal = ( i.triptr->datatype &4 ? 
							embree::normalize((normals[t.n1]*i.u)+(normals[t.n2]*i.v) + (1-i.u-i.v)*normals[t.n0])  //interpolated
							: i.triptr->normal); //flat
			i.abs_pos = p0 + i.t*p1;
			i.incoming = p1;
		}
		return i;

	}
	const std::string dummy = "error";
	//finds mat name from the pointer - expensive and only done one time upon user request
	const std::string& scene::material_name(material_ptr mat) const
	{
		for(material_cont::const_iterator it = this->materials.begin(); it != materials.end(); ++it)
		{
			if(it->second == mat) return it->first;
		}
		return dummy;
	}

//configurators	
	configurable* scene::get_conf_target(const std::string& name)
	{
		if(name == "caustic") 
			return &lightpaths;
		if(name == "conv")
			return &conv;
		material_cont::iterator mat = materials.find(name);
		if(mat!= materials.end()) return mat->second;
		return 0;
	}
	
    void scene::announce_properties(const std::string& prefix, property_announcer announcer)
	{
		
		announcer((prefix+".primary_lighting").c_str(), ANNOUNCED_STRING, 
			(lighting_mode & LM_PRIMARY_LIGHTSOURCE) ? "lightsource" : (
			(lighting_mode & LM_PRIMARY_BRDF)        ? "brdf" : (
			(lighting_mode & LM_PRIMARY_MIS)         ? "mis" : 
			"error"))
		);
		announcer((prefix+".caustic_lighting").c_str(), ANNOUNCED_STRING, 
			(lighting_mode & LM_CAUSTIC_LIGHTSOURCE) ? "lightsource" : (
			(lighting_mode & LM_CAUSTIC_BRDF)        ? "brdf" : (
			(lighting_mode & LM_CAUSTIC_MIS)         ? "mis" : 
			"error"))
		);
		lightpaths.announce_properties(prefix+".caustic", announcer);

		for(material_cont::iterator mat = materials.begin(); mat != materials.end(); ++mat)
			mat->second->announce_properties(prefix+"."+mat->first, announcer);
		conv.announce_properties(prefix+".conv", announcer);
	}
	err_t scene::set_string(std::string& name, const std::string& value)
	{
		err_t res = configurable::set_string(name, value);
		if(res == ERR_OK_RESET_CAUSTICS)
		{	//if configuration (refraction indices, bsdf type) breaks caustics data, turn them off
			lighting_mode = (lighting_mode & LM_PRIMARY) | LM_CAUSTIC_BRDF;
			return ERR_OK;
		}
		return res;
	}
	err_t scene::set_my_string(std::string& name, const std::string& value)
	{
		if(name == "primary_lighting")
		{
			unsigned int nl = 
				value == "lightsource"	? LM_PRIMARY_LIGHTSOURCE :
				value == "brdf"			? LM_PRIMARY_BRDF:
				value == "mis"			? LM_PRIMARY_MIS:
				/*error*/				  LM_PRIMARY_LIGHTSOURCE;
			this->lighting_mode = (lighting_mode & LM_CAUSTIC) | nl;
			return ERR_OK;
		}
		else if(name == "caustic_lighting")
		{
			unsigned int nl = 
				value == "lightsource"	? LM_CAUSTIC_LIGHTSOURCE :
				value == "brdf"			? LM_CAUSTIC_BRDF:
				value == "mis"			? LM_CAUSTIC_MIS:
				/*error*/				  LM_CAUSTIC_BRDF;
			this->lighting_mode = (lighting_mode & LM_PRIMARY) | nl;
			if(!(nl & LM_CAUSTIC_BRDF)) lightpaths.find_paths(this);
			return ERR_OK;
		}
		else return ERR_BAD_PROP;
	}
}